package org.opmviewer.core.views;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import org.opmviewer.graphviz.MyNodeRenderer;
import org.opmviewer.prefuse.LabelLayout2;
import org.opmviewer.prefuse.Overview;
import org.opmviewer.prefuse.OverviewPaintListener;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.Action;
import prefuse.action.ActionList;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.LocationAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.assignment.StrokeAction;
import prefuse.action.layout.graph.RadialTreeLayout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.ControlAdapter;
import prefuse.controls.FocusControl;
import prefuse.controls.PanControl;
import prefuse.controls.ToolTipControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Schema;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.display.DisplayLib;
import prefuse.util.display.ItemBoundsListener;
import prefuse.util.ui.JFastLabel;
import prefuse.util.ui.JSearchPanel;
import prefuse.visual.DecoratorItem;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;

/**
 * Demonstration of a node-link graph viewer
 *
 * @version 1.0
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class RadialView extends Display {

    public static final String graph_CHI = "/home/grzegorz/Desktop/pref/prefuse-beta/data/chi-ontology.xml.gz";
    private static final String graph = "graph";
    private static final String NODES = "graph.nodes";
    private static final String EDGES = "graph.edges";
    private static final String EDGE_DECORATORS = "edgeDecs";
    private static final String NODE_DECORATORS = "nodeDecs";
    private Renderer m_nodeRenderer;
    private EdgeRenderer edgeRenderer;
    private int m_orientation = Constants.ORIENT_LEFT_RIGHT;
    private static final Schema DECORATOR_SCHEMA = PrefuseLib.getVisualItemSchema();

    static {
        DECORATOR_SCHEMA.setDefault(VisualItem.INTERACTIVE, false);
        DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(128));
        DECORATOR_SCHEMA.setDefault(VisualItem.FONT, FontLib.getFont("Tahoma", 16));
    }

    public RadialView(Graph g, String label) {
        super(new Visualization());
        VisualGraph vg = m_vis.addGraph(graph, g);
        vg.getNodes().addColumn("LABEL", label);
        vg.getEdges().addColumn("LABEL", "'edge'");

        m_nodeRenderer = new MyNodeRenderer();
        edgeRenderer = new EdgeRenderer();

        DefaultRendererFactory rf = new DefaultRendererFactory(m_nodeRenderer);
        m_vis.setRendererFactory(rf);


        //rf.add(new InGroupPredicate(EDGE_DECORATORS), new LabelRenderer("LABEL"));
        rf.add(new InGroupPredicate(NODE_DECORATORS), new LabelRenderer("LABEL"));

        // adding decorators, one group for the nodes, one for the edges

        DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(128));
        m_vis.addDecorators(NODE_DECORATORS, NODES, DECORATOR_SCHEMA);
        //locate the provenance root
        Predicate p = ExpressionParser.predicate("TYPE = 'root'");
        Iterator iter = m_vis.items(NODES, p);
        VisualItem rootItem = (VisualItem) iter.next();
        //set initial focus to root
        m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(rootItem);

        // colors
        ItemAction nodeColor = new NodeColorAction(NODES);
        ItemAction edgeColor = new ColorAction(EDGES,
                VisualItem.STROKECOLOR, ColorLib.rgb(100, 100, 100));
        ItemAction edgeArrowColor = new ColorAction(EDGES,
                VisualItem.FILLCOLOR, ColorLib.rgb(100, 100, 100));
        BasicStroke bs = new BasicStroke(1);
        ItemAction nodeStroke = new StrokeAction(NODES, bs);
        ItemAction textColor = new TextColorAction(NODE_DECORATORS);
        ItemAction nodeStrokeColor = new ColorAction(NODES, VisualItem.STROKECOLOR,
                ColorLib.rgb(30, 30, 30));
        m_vis.putAction("textColor", textColor);
        m_vis.putAction("nodeColor", nodeColor);

        // quick repaint
        ActionList repaint = new ActionList();
        repaint.add(nodeColor);
        repaint.add(edgeColor);
        repaint.add(edgeArrowColor);
        repaint.add(nodeStroke);
        repaint.add(nodeStrokeColor);
        repaint.add(new RepaintAction());
        m_vis.putAction("repaint", repaint);

        // full paint
        ActionList fullPaint = new ActionList();
        fullPaint.add(nodeColor);
        repaint.add(edgeColor);
        repaint.add(edgeArrowColor);
        m_vis.putAction("fullPaint", fullPaint);

        // animate paint change
        ActionList animatePaint = new ActionList(400);
        animatePaint.add(new ColorAnimator(NODES));
        animatePaint.add(new ColorAnimator(NODE_DECORATORS));
        animatePaint.add(new RepaintAction());
        m_vis.putAction("animatePaint", animatePaint);

        // create the graph layout action
        RadialTreeLayout graphLayout = new RadialTreeLayout(graph);
        
        //divide edges into ones that reach deeper into the graph and ones that
        //are directed towards the root node; register a different edge
        //renderer for each kind

        Iterator edgeIt = m_vis.items(new InGroupPredicate(EDGES));
        ActionList layout = new ActionList();
        layout.add(graphLayout);
        layout.add(new LabelLayout2(NODE_DECORATORS));

        m_vis.putAction("layout", layout);

        AutoPanAction autoPan = new AutoPanAction();

        // create the filtering and layout
        ActionList filter = new ActionList();
        filter.add(new FontAction(NODES, FontLib.getFont("Tahoma", 14)));
        //filter.add(new FisheyeDAGFilter(graph, 2, (NodeItem) rootItem));
        filter.add(layout);
        filter.add(textColor);
        filter.add(nodeColor);
        filter.add(edgeColor);
        filter.add(edgeArrowColor);
        filter.add(nodeStroke);
        filter.add(nodeStrokeColor);
        m_vis.putAction("filter", filter);

        //animated transition
        ActionList animate = new ActionList(1000);
        animate.setPacingFunction(new SlowInSlowOutPacer());
        animate.add(autoPan);
        animate.add(new QualityControlAnimator());
        animate.add(new VisibilityAnimator(graph));
        animate.add(new LocationAnimator(NODES));
        animate.add(new ColorAnimator(NODES));
        animate.add(new RepaintAction());
        m_vis.putAction("animate", animate);
        m_vis.alwaysRunAfter("filter", "animate");

        // create animator for orientation changes
        //ActionList orient = new ActionList(2000);
        //orient.setPacingFunction(new SlowInSlowOutPacer());
        //orient.add(autoPan);
        //orient.add(new QualityControlAnimator());
        //orient.add(new LocationAnimator(NODES));
        //orient.add(new RepaintAction());
        //m_vis.putAction("orient", orient);

        // ------------------------------------------------

        // initialize the display
        //setSize(700, 600);
        addControlListener(new ZoomToFitControl());
        addControlListener(new ZoomControl());
        addControlListener(new WheelZoomControl());
        addControlListener(new PanControl());
        //addControlListener(new DragControl());
        addControlListener(new FocusControl(1, "filter"));
        m_vis.run("layout");
        m_vis.run("filter");

        TupleSet search = new PrefixSearchTupleSet();
        m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, search);
        search.addTupleSetListener(new TupleSetListener() {

            public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
                m_vis.cancel("animatePaint");
                m_vis.run("fullPaint");
                m_vis.run("animatePaint");
            }
        });
    }

    public static void demo(Graph g, final String label,
            JPanel panel, JPanel opanel) {
        Color BACKGROUND = new Color(209, 238, 238);
        Color FOREGROUND = Color.BLACK;
        // create a new graphmap
        final RadialView view = new RadialView(g, label);
        view.setBackground(BACKGROUND);
        view.setForeground(FOREGROUND);
        // create a search panel for the graph map
        JSearchPanel search = new JSearchPanel(view.getVisualization(),
                NODES, Visualization.SEARCH_ITEMS, "LABEL", true, true);
        search.setShowResultCount(true);
        search.setBorder(BorderFactory.createEmptyBorder(5, 5, 4, 0));
        search.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11));
        search.setBackground(Color.WHITE);
        search.setForeground(FOREGROUND);

        final JFastLabel title = new JFastLabel("OPM Viewer prototype");
        title.setPreferredSize(new Dimension(350, 20));
        title.setVerticalAlignment(SwingConstants.BOTTOM);
        title.setBorder(BorderFactory.createEmptyBorder(3, 0, 0, 0));
        title.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 16));
        title.setBackground(Color.WHITE);
        title.setForeground(FOREGROUND);

        view.addControlListener(new ControlAdapter() {

            @Override
            public void itemEntered(VisualItem item, MouseEvent e) {
                if (item.canGetString(label)) {
                    title.setText(item.getString(label));
                }
            }

            @Override
            public void itemExited(VisualItem item, MouseEvent e) {
                title.setText(null);
            }
        });
        view.addControlListener(new ToolTipControl("TYPE"));


        Box box = new Box(BoxLayout.X_AXIS);
        box.setBackground(Color.WHITE);
        box.add(Box.createHorizontalStrut(10));
        box.add(title);
        box.add(Box.createHorizontalGlue());
        box.add(search);
        box.add(Box.createHorizontalStrut(3));

        // overview display
        Overview overview = new Overview(view);
        view.addPaintListener(new OverviewPaintListener(overview));
        overview.setBackground(BACKGROUND);
        overview.setSize(290, 290);
        opanel.add(overview);
        panel.add(view, BorderLayout.CENTER);
        panel.add(box, BorderLayout.SOUTH);
        //return panel;
    }

    // ------------------------------------------------------------------------
    public class AutoPanAction extends Action {

        private Point2D m_start = new Point2D.Double();
        private Point2D m_end = new Point2D.Double();
        private Point2D m_cur = new Point2D.Double();
        private int m_bias = 150;

        public void run(double frac) {
            TupleSet ts = m_vis.getFocusGroup(Visualization.FOCUS_ITEMS);
            if (ts.getTupleCount() == 0) {
                return;
            }

            if (frac == 0.0) {
                int xbias = 0, ybias = 0;
                /* switch (m_orientation) {
                case Constants.ORIENT_LEFT_RIGHT:
                xbias = m_bias;
                break;
                case Constants.ORIENT_RIGHT_LEFT:
                xbias = -m_bias;
                break;
                case Constants.ORIENT_TOP_BOTTOM:
                ybias = m_bias;
                break;
                case Constants.ORIENT_BOTTOM_TOP:
                ybias = -m_bias;
                break;
                }
                 */

                ybias = m_bias;

                VisualItem vi = (VisualItem) ts.tuples().next();
                m_cur.setLocation(getWidth() / 2, getHeight() / 2);
                getAbsoluteCoordinate(m_cur, m_start);
                m_end.setLocation(vi.getX() + xbias, vi.getY() + ybias);
            } else {
                m_cur.setLocation(m_start.getX() + frac * (m_end.getX() - m_start.getX()),
                        m_start.getY() + frac * (m_end.getY() - m_start.getY()));
                panToAbs(m_cur);
            }
        }
    }

    public static class NodeColorAction extends ColorAction {

        public NodeColorAction(String group) {
            super(group, VisualItem.FILLCOLOR);
        }

        @Override
        public int getColor(VisualItem item) {
            if (m_vis.isInGroup(item, Visualization.SEARCH_ITEMS)) {
                return ColorLib.rgb(255, 190, 190);
            } else if (m_vis.isInGroup(item, Visualization.FOCUS_ITEMS)) {
                if (item.getString("TYPE").equals("artifact")) {
                    return ColorLib.rgb(124, 205, 124);
                } else if (item.getString("TYPE").equals("process")) {
                    return ColorLib.rgb(204, 190, 112);
                } else {
                    return ColorLib.rgb(205, 150, 205);
                }
            } else {
                if (item.getString("TYPE").equals("artifact")) {
                    return ColorLib.rgb(144, 238, 144);
                } else if (item.getString("TYPE").equals("process")) {
                    return ColorLib.rgb(255, 236, 139);
                } else {
                    return ColorLib.rgb(238, 174, 238);
                }
            }
        }
    } // end of inner class graphMapColorAction

    public static class TextColorAction extends ColorAction {

        public TextColorAction(String group) {
            super(group, VisualItem.TEXTCOLOR);
        }

        @Override
        public int getColor(VisualItem item) {
            DecoratorItem di = (DecoratorItem) item;
            if (m_vis.isInGroup(di.getDecoratedItem(), Visualization.FOCUS_ITEMS)) {
                return ColorLib.rgb(255, 255, 255);
            }
            return ColorLib.rgb(50, 50, 50);
        }
    }

    public static class NodeStrokeAction extends ColorAction {

        public NodeStrokeAction(String group) {
            super(group, VisualItem.STROKECOLOR);
        }

        @Override
        public int getColor(VisualItem item) {
            if (!m_vis.isInGroup(item, Visualization.FOCUS_ITEMS)) {
                return ColorLib.rgb(0, 0, 0);
            }
            return ColorLib.rgb(255, 218, 185);

        }
    }

    public static class FitOverviewListener implements ItemBoundsListener {

        private Rectangle2D m_bounds = new Rectangle2D.Double();
        private Rectangle2D m_temp = new Rectangle2D.Double();
        private double m_d = 15;

        public void itemBoundsChanged(Display d) {
            d.getItemBounds(m_temp);
            GraphicsLib.expand(m_temp, 25 / d.getScale());

            double dd = m_d / d.getScale();
            double xd = Math.abs(m_temp.getMinX() - m_bounds.getMinX());
            double yd = Math.abs(m_temp.getMinY() - m_bounds.getMinY());
            double wd = Math.abs(m_temp.getWidth() - m_bounds.getWidth());
            double hd = Math.abs(m_temp.getHeight() - m_bounds.getHeight());
            if (xd > dd || yd > dd || wd > dd || hd > dd) {
                m_bounds.setFrame(m_temp);
                DisplayLib.fitViewToBounds(d, m_bounds, 0);
            }
        }
    }
}

